/*************************************************************************
 * Copyright 2008 David Almilli
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *      
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 **************************************************************************/
package com.almilli.catnip.server;

import java.io.IOException;
import java.net.SocketTimeoutException;
import org.apache.coyote.InputBuffer;
import org.apache.coyote.Request;
import org.apache.coyote.Response;
import org.apache.coyote.http11.InputFilter;
import org.apache.tomcat.util.buf.ByteChunk;

/**
 * The <code>HmeInputFilter</code> class will continually read from the stream as long as the
 * stream remains open.  It will ignore the socket timeout if the response content type matches
 * application/x-hme and it still needs to read more data from the input stream.  This allows
 * an HME application to block on the event stream and won't get disconnected after the socket
 * timeout occurs.
 * @author almilli
 */
public class HmeInputFilter implements InputFilter {
	public static final String HME_MIME_TYPE = "application/x-hme";
	
	private static boolean registeredHook;
	private static boolean shuttingDown;

    protected static final String ENCODING_NAME = "hme";
    protected static final ByteChunk ENCODING = new ByteChunk();
    
    static {
        ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
    }
    
    /**
     * Next buffer in the pipeline.
     */
    protected InputBuffer buffer;
    
    private Response response;
    
    {
    	if (!registeredHook) {
    		registeredHook = true;
    		try {
    			Runtime.getRuntime().addShutdownHook(new Shutdown());
    		} catch (Exception e) {
    			//ignore
    		}
    	}
    }


    /**
     * Read bytes.
     * 
     * @return If the filter does request length control, this value is
     * significant; it should be the number of bytes consumed from the buffer,
     * up until the end of the current request body, or the buffer length, 
     * whichever is greater. If the filter does not do request body length
     * control, the returned value should be -1.
     */
    public int doRead(ByteChunk chunk, Request req)
        throws IOException {
    	int result = -1;
        boolean done = false;
    	while (!done) {
    		//keep looping until we read something, if we get a socket timeout and it's an HME app
    		//then we need to read again because it's a constant stream.
	    	try {
	    		result = buffer.doRead(chunk, req);
                done = (result > 0 || result == -1);
	    	} catch (SocketTimeoutException t) {
	    		if (shuttingDown || !HME_MIME_TYPE.equals(response.getContentType())) {
	    			//re-throw because it isn't an hme stream
	    			throw t;
	    		}
	    	}
    	}
    	return result;

    }


    // ---------------------------------------------------- InputFilter Methods


    /**
     * Read the content length from the request.
     */
    public void setRequest(Request request) {
    }
    
    public void setResponse(Response response) {
    	this.response = response;
    }


    /**
     * End the current request.
     */
    public long end() throws IOException {
        return 0;

    }


    /**
     * Amount of bytes still available in a buffer.
     */
    public int available() {
        return 0;
    }
    

    /**
     * Set the next buffer in the filter pipeline.
     */
    public void setBuffer(InputBuffer buffer) {
        this.buffer = buffer;
    }


    /**
     * Make the filter ready to process the next request.
     */
    public void recycle() {
    }


    /**
     * Return the name of the associated encoding; Here, the value is 
     * "identity".
     */
    public ByteChunk getEncodingName() {
        return ENCODING;
    }

    private static class Shutdown extends Thread {
    	public Shutdown() {
    		super("HME Input Filter Shutdown");
    	}
    	
    	public void run() {
    		shuttingDown = true;
    	}
    }

}
